package org.hilo.boot.scaffold;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.hilo.boot.core.UT;
import org.jretty.util.Const;
import org.jretty.util.FileUtils.TextFileParse;
import org.jretty.util.NestedRuntimeException;


/**
 * 开发框架 脚手架生成器
 * 
 * @author zollty
 * @since 2020-5-12
 */
public class ScaffoldGenerator extends AbstractGenerator {
    public static final String TYPE_DUBBO = "dubbo";
    public static final String TYPE_ADMIN = "admin";
    
    public static final String STRUCT_SINGL = "single";
    public static final String STRUCT_SPLIT = "split";
    public static final String STRUCT_MULTI = "multi";
    
    protected static final String SOURCE_FILE_URL = "http://10.2.10.51:8081/nexus/content/repositories/releases/"
            + "org/hilo/framework/hilodemo-single-source/1/hilodemo-single-source-1.zip";
    
    protected String sourceProjectName = "hilodemo-single";
    
    protected String copyFileToPath;
    
    protected List<PluginGenerator> plugins = new LinkedList<PluginGenerator>(); 
    
    public ScaffoldGenerator() {
        this.separateBizApp = true;
        this.sourceFileUrl = SOURCE_FILE_URL;
    }
    
    public ScaffoldGenerator(String groupId, String artifactId, String basePackage) {
        this.sourceFileUrl = SOURCE_FILE_URL;
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.basePackage = basePackage;
        this.separateBizApp = true;
    }

    public ScaffoldGenerator demoInstance() {
        DemoProjectGenerator that = new DemoProjectGenerator(groupId, artifactId, basePackage);
        that.separateBizApp = this.separateBizApp;
        that.useType = this.useType;
        that.copyFileToPath = this.copyFileToPath;
        that.source = this.source;
        return that;
    }
    
    public ScaffoldGenerator demoInstance(String copyFileToPath) {
        if (copyFileToPath != null && new File(copyFileToPath).exists()) {
            throw new IllegalStateException("copyFileToPath已存在，默认不覆盖，请指定一个新路径");
        }
        DemoProjectGenerator that = new DemoProjectGenerator(groupId, artifactId, basePackage);
        that.separateBizApp = this.separateBizApp;
        that.useType = this.useType;
        that.copyFileToPath = copyFileToPath;
        that.source = this.source;
        return that;
    }

    public byte[] generatorFile() {
        
        if(UT.Str.isBlank(groupId)) {
            groupId = "com.zollty.hilo";
        }
        if(UT.Str.isBlank(artifactId)) {
            artifactId = "project";
            if(UT.Str.isBlank(basePackage)) {
                basePackage = groupId + "." + artifactId;
            }
        }
        
        String rpath = UT.Random.getRadomStr09az(10);
        File path = SgTools.createTempDir(rpath);
        path.deleteOnExit();
        logger.info("------------------path=" + path);
        
        String target = UT.Path.normalPath(path.getAbsolutePath() + "/" + artifactId + "-parent/");
        String targetZip = UT.Path.normalPath(path.getAbsolutePath() + "/" + artifactId + "-parent.zip");
        deleteAll(target);
        new File(target).mkdir();
        
        if (source == null) {
            String sourceNewZipPath = UT.Path.normalPath(path.getAbsolutePath() + "/hilodemo-source.zip");
            if(getSourceFile(sourceNewZipPath)) {
                source = sourceNewZipPath;
            } else {
                UT.File.deleteAll(path);
                throw new NestedRuntimeException("source is null");
            }
        }
        if (source.endsWith(".zip")) {
            String sourceNewPath = UT.Path.normalPath(path.getAbsolutePath() + "/hilodemo-source/");
            try {
                ZipUtils.unzipFile(sourceNewPath, source);
                source = sourceNewPath;
            } catch (Exception e) {
                UT.File.deleteAll(path);
                throw new NestedRuntimeException(e);
            }
        }
        
        if (basePackage == null) {
            basePackage = groupId;
        }
        basePackage = UT.Str.replace(basePackage, "-", ".");
        targetBasePath = target;
        try {
            if(!build(target)) {
                UT.File.deleteAll(path);
                return null;
            }
        } catch (Exception e) {
            logger.error(e, "Unexpected exception!");
            UT.File.deleteAll(path);
            return null;
        }
        
        try {
            ZipUtils.zipFile(targetZip, target);
        } catch (Exception e) {
            logger.error(e, "zipFile fail");
        }

        deleteAll(target);

        logger.info("----------------------------------------done!");
        byte[] bret = null;
        try {
            bret = FileUtils.readFileToByteArray(new File(targetZip));
        } catch (IOException e) {
            logger.error(e, "readFile fail");
        }

        new File(targetZip).delete();
        UT.File.deleteAll(path);

        return bret;
    }
    
    protected String starterProjectPath() {
//        if (separateBizApp) {
//            return targetBasePath + artifactId + "/";
//        }
        return targetBasePath + artifactId + "/";
    }
    
    protected String bizProjectPath() {
        return targetBasePath + artifactId + "-biz/";
    }
    
    // 入口
    boolean build(String target) {
        String newPack = basePackage;
        String newPath = UT.Str.replace(newPack, ".", "/");
        
        if (!demoMode) {
            generateEmpty(target, newPath);
            commonBuild(target);

            if (useType.equals("admin")) {
                buildAdmin(target, newPath);
            } else if (useType.equals("dubbo")) {
                buildDubbo(target, newPath);
            } else {
                //UT.File.deleteAll(path);
                return false;
            }
            
        } else {
            generateEmpty(target, newPath);
            commonBuild(target);
            
            if (useType.equals("admin")) {
                buildAdmin(target, newPath);
            } else if (useType.equals("dubbo")) {
                buildDubbo(target, newPath);
            } else if (useType.equals("web")) {
                buildWeb(target, newPath);
            } else {
                //UT.File.deleteAll(path);
                return false;
            }
        }
        
        for (PluginGenerator plugin : plugins) {
            try {
                plugin.config(groupId, artifactId, basePackage, separateBizApp, 
                        target, starterProjectPath(), bizProjectPath());
                plugin.prepareAndMergeSource();
                pluginBuild(target, plugin);
            } finally {
                plugin.clean();
            }
        }
        
        if (copyFileToPath != null) {
            UT.File.copyFolder(target, copyFileToPath);
            //UT.File.deleteAll(path);
            SgTools.open(copyFileToPath);
            return false;
        }
        
        return true;
    }
    
    void buildWeb(String target, String newPath) {
        
    }
    
    static final String OLD_PATH = "io/hilo/example";
    static final String OLD_PACK = "io.hilo.example";
    
    void generateEmpty(String target, String newPath) {
        String newPack = newPath.replace('/', '.');
        TextFileParse<String> parser = new TextFileParse<String>() {
            @Override
            public String parseOneLine(String line) {
                return line.replaceAll(OLD_PACK, newPack)
                        .replaceAll(sourceProjectName, artifactId)
                        .replaceAll("org.hilo.bootdemo", groupId);
            }
        };
        
        String sourceDir = UT.Path.normalPath(source);
        deleteAll(target);
        List<File> files = SgTools.loopFiles(new File(sourceDir), null); 
        for (File file : files) {
            String fileFullPath = UT.Path.normalPath(file.getAbsolutePath());
            String targetPath = fileFullPath.replace(sourceDir, target);
            targetPath = targetPath.replace(OLD_PATH, newPath);
            targetPath = targetPath.replace(sourceProjectName, artifactId);
            SgTools.parseTextFileAndWrite2Target(fileFullPath, parser, targetPath);
        }
    }
    
    void commonBuild(String target) {
        deleteAll(target + artifactId + "-app/");
        deleteIDEFiles(target);
        deleteIDEFiles(target + artifactId + "/");
        deleteIDEFiles(target + artifactId + "-common/");
        deleteIDEFiles(target + artifactId + "-biz/");
        deleteIDEFiles(target + artifactId + "-api/");
        
        renameTo(target + "pom-app.xml", target + "pom.xml");
        
        Rep.build(target + artifactId + "/src/main/resources/META-INF/app.properties", null)
        .replace("fast4ui", artifactId).apply();
        
        Rep.build(target + artifactId +"/src/main/resources/application.yml", null)
        .replace("hilo-demo", artifactId)
        .replace(EXA_PACKAGE, basePackage)
        .apply();
        
        Rep.build(target + artifactId + "/src/main/resources/application-dubbo.yml", null)
        .replace("hilo-demo", artifactId).apply();
        
        if (separateBizApp) { // 配置分离型项目
            // pom操作： 添加biz信息
            String ts1 = "<dependency>\r\n        <groupId>" + groupId
                    + "</groupId>\r\n        <artifactId>" + artifactId
                    + "-biz</artifactId>\r\n        <version>1.0.0-SNAPSHOT</version>\r\n      </dependency>";
            Rep.build(target + "pom.xml", null)
            .replace("<!-- biz Marker -->", ts1)
            .replace("<module>" + artifactId + "</module><!-- m Marker -->", "<module>" + artifactId + "</module>")
            .replace("<module>" + artifactId + "-biz</module><!-- m Marker -->", "<module>" + artifactId + "-biz</module>")
            .apply();
            
        } else { // 大锅型项目
            //new File(targetDir + artifactId + "-app/src").mkdirs();
            UT.File.copyFolder(target + artifactId + "-biz/src", target + artifactId + "/src");
            deleteAll(target + artifactId + "-biz/");
            
            // pom操作： 删除biz等信息
            Rep.build(target + "pom.xml", null)
            .deleteLine("    <module>" + artifactId + "-biz</module><!-- m Marker -->")
            .deleteLine("      <!-- biz Marker -->")
            .replace("<module>" + artifactId + "</module><!-- m Marker -->", "<module>" + artifactId + "</module>")
            .apply();
        }
    }
    
    
    static final String POM_POS_FLAG = "    <!-- plugin Marker -->";
    // 往pom.xml等标准文件中添加组件
    void pluginBuild(String target, PluginGenerator plugin) {
        
        String ext = plugin.addParentPomManagementDependency();
        if (UT.Str.isNotEmpty(ext)) {
            Rep.build(target + "pom.xml", null)
                .replace("    </dependencies>", ext + "\r\n    </dependencies>")
                .apply();
        }
        
        if (separateBizApp) { // 配置分离型项目
            ext = plugin.addStarterPomManagementDependency();
            if (UT.Str.isNotEmpty(ext)) {
                Rep.build(starterProjectPath() + "pom.xml", null)
                    .replace(POM_POS_FLAG, ext + Const.CRLF + Const.CRLF + POM_POS_FLAG)
                    .apply();
            }
            ext = plugin.addBizPomManagementDependency();
            if (UT.Str.isNotEmpty(ext)) {
                Rep.build(bizProjectPath() + "pom.xml", null)
                    .replace(POM_POS_FLAG, ext + Const.CRLF + Const.CRLF + POM_POS_FLAG)
                    .apply();
            }
            
            ext = plugin.addDevYamlConfig();
            if (UT.Str.isNotEmpty(ext)) {
                Rep.build(starterProjectPath()+ "src/main/resources/application-dev.yml", null)
                    .append(ext)
                    .apply();
            }
            
        } else { // 大锅型项目

            ext = plugin.addBizPomManagementDependency();
            String ext2 = plugin.addStarterPomManagementDependency();
            if (UT.Str.isNotEmpty(ext)) {
                if (ext2 != null) {
                    ext = ext + Const.CRLF + Const.CRLF + ext2;
                }
            } else {
                ext = ext2;
            }
            if (ext != null) {
                Rep.build(target + artifactId + "/pom.xml", null)
                    .replace(POM_POS_FLAG, ext + Const.CRLF + Const.CRLF + POM_POS_FLAG)
                    .apply();
            }
            
            ext = plugin.addDevYamlConfig();
            if (UT.Str.isNotEmpty(ext)) {
                Rep.build(target + artifactId + "/src/main/resources/application-dev.yml", null)
                    .append(ext)
                    .apply();
            }
        }
    }
    
    public void addPluginGenerator(PluginGenerator pluginGenerator) {
        this.plugins.add(pluginGenerator);
    }
    
    static final String WAR_DEP = "<!-- starter-test = junit + spring-test + mockito -->\r\n    <dependency>\r\n      <groupId>org.springframework.boot</groupId>\r\n      <artifactId>spring-boot-starter-test</artifactId>\r\n      <scope>test</scope>\r\n    </dependency>\r\n    \r\n    <!-- starter-web = spring-webmvc + logback + tomcat -->\r\n    <dependency>\r\n      <groupId>org.springframework.boot</groupId>\r\n      <artifactId>spring-boot-starter-web</artifactId>\r\n      <exclusions>\r\n        <!-- war包部署，将默认的tomcat依赖改成provided -->\r\n        <exclusion>\r\n          <groupId>org.springframework.boot</groupId>\r\n          <artifactId>spring-boot-starter-tomcat</artifactId>\r\n        </exclusion>\r\n      </exclusions>\r\n    </dependency>\r\n    <dependency>\r\n      <groupId>org.springframework.boot</groupId>\r\n      <artifactId>spring-boot-starter-tomcat</artifactId>\r\n      <scope>provided</scope>\r\n    </dependency>\r\n    <dependency>\r\n      <groupId>org.apache.tomcat</groupId>\r\n      <artifactId>tomcat-jdbc</artifactId>\r\n      <scope>provided</scope>\r\n    </dependency>\r\n    <!-- end starter-web -->";
    static final String EXA_PACKAGE = "io.hilo.example";
    void buildAdmin(String target, String newPath) {
        // 删除api
        Rep.build(target + "pom.xml", null)
        .deleteLine("      <!-- api Marker -->")
        .deleteLine("    <module>" + artifactId + "-api</module><!-- m Marker -->")
        .apply();
        
        String subTarget;
        if (separateBizApp) { // 配置分离型项目
            subTarget = target + artifactId + "/";
            deleteAll(subTarget + "pom-dubbo.xml");
            deleteAll(subTarget + "pom-admin.xml");
            
            subTarget = target + artifactId + "-biz/";
            deleteAll(subTarget + "pom-dubbo.xml");
            renameTo(subTarget + "pom-admin.xml", subTarget + "pom.xml");
            
            subTarget = target + artifactId + "/";
            
        } else { // 大锅型项目
            subTarget = target + artifactId + "/";
            deleteAll(subTarget + "pom-dubbo.xml");
            renameTo(subTarget + "pom-admin.xml", subTarget + "pom.xml");
            Rep.build(subTarget + "pom.xml", null)
            .replace("<!-- war Marker -->", WAR_DEP).apply();
        }
        
        Rep.build(subTarget+"src/main/java/" + newPath + "/StartApplicationAdmin.java", 
                subTarget+"src/main/java/" + newPath + "/StartApplication.java")
        .replace("StartApplicationAdmin", "StartApplication").apply();
        deleteAll(subTarget+"src/main/java/" + newPath + "/StartApplicationDubbo.java");
        
        Rep.build(subTarget+"src/main/resources/application.yml", null)
            .replaceBetween("spring.profiles.include: ", Const.CRLF, "base,dbbase,dbweb")
            .apply();
        
        deleteAll(subTarget + "src/main/resources/application-dubbo.yml");

        subTarget = target + artifactId + "-api/";
        deleteAll(subTarget);
    }

    void buildDubbo(String target, String newPath) {
        // 添加api
        String ts0 = "<dependency>\r\n        <groupId>" + groupId
                + "</groupId>\r\n        <artifactId>" + artifactId
                + "-api</artifactId>\r\n        <version>1.0.0-SNAPSHOT</version>\r\n      </dependency>";
        Rep.build(target + "pom.xml", null)
        .deleteLine("      <!-- biz Marker -->")
        .replace("<module>" + artifactId + "-api</module><!-- m Marker -->", "<module>" + artifactId + "-api</module>")
        .replace("<!-- api Marker -->", ts0)
        .apply();
        String subTarget;
        if (separateBizApp) { // 配置分离型项目
            subTarget = target + artifactId + "/";
            deleteAll(subTarget + "pom-dubbo.xml");
            deleteAll(subTarget + "pom-admin.xml");
            
            subTarget = target + artifactId + "-biz/";
            deleteAll(subTarget + "pom-admin.xml");
            renameTo(subTarget + "pom-dubbo.xml", subTarget + "pom.xml");
            
            subTarget = target + artifactId + "/";
            
        } else { // 大锅型项目
            subTarget = target + artifactId + "/";
            deleteAll(subTarget + "pom-admin.xml");
            renameTo(subTarget + "pom-dubbo.xml", subTarget + "pom.xml");
            Rep.build(subTarget + "pom.xml", null)
            .replace("<!-- war Marker -->", WAR_DEP)
            .apply();
            
            deleteAll(subTarget + "src/main/resources/public");
            deleteAll(subTarget + "src/main/resources/static");
            deleteAll(subTarget + "src/main/webapp");
        }
        
        Rep.build(subTarget + "src/main/java/" + newPath + "/StartApplicationDubbo.java",
                subTarget + "src/main/java/" + newPath + "/StartApplication.java")
                .replace("StartApplicationDubbo", "StartApplication")
                .apply();
        deleteAll(subTarget + "src/main/java/" + newPath + "/StartApplicationAdmin.java");
        deleteAll(subTarget + "src/main/java/" + newPath + "/app/ServletInitializer.java");
        deleteAll(subTarget + "src/main/java/" + newPath + "/app/AppFilterConfig.java");
        
        Rep.build(subTarget + "src/main/resources/application.yml", null)
                .replaceBetween("active: ", Const.CRLF, "dev,log,dubbo")
                .replaceBetween("spring.profiles.include: ", Const.CRLF, "base,dbbase").apply();
//                .deleteStartLine("server.port:") // 由于生产都需要tomcat部署，所以server不能少
        
        Rep.build(subTarget + "src/main/resources/application-dev.yml", null)
                .deleteStartLine("# 针对Web项目")
                .deleteStartLine("hilo.swagger.base-packages").apply();
        
        // API 
//        subTarget = target + artifactId + "-api/";
//        Rep.build(subTarget + "pom.xml", null)
//        .replace("<groupId>org.hilo.boot</groupId><!-- Marker -->", "<groupId>" + groupId + "</groupId>").apply();
    }

    
//    /**
//     * 自定义本地资源路径
//     */
//    public void setSource(String source) {
//        this.source = source;
//    }


//    /**
//     * @param sourceFileUrl the sourceFileUrl to set
//     */
//    public void setSourceFileUrl(String sourceFileUrl) {
//        this.sourceFileUrl = sourceFileUrl;
//    }
    
    /**
     * @param useType the useType to set
     */
    public void setUseType(String useType) {
        this.useType = useType;
    }

    /**
     * @param groupId the groupId to set
     */
    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    /**
     * @param artifactId the artifactId to set
     */
    public void setArtifactId(String artifactId) {
        this.artifactId = artifactId;
    }

    /**
     * @param basePackage the basePackage to set
     */
    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * @param copyFileToPath the copyFileToPath to set
     */
    public void setCopyFileToPath(String copyFileToPath) {
        if (copyFileToPath != null && new File(copyFileToPath).exists()) {
            throw new IllegalStateException("copyFileToPath已存在，默认不覆盖，请指定一个新路径");
        }
        this.copyFileToPath = copyFileToPath;
    }

    /**
     * 业务和配置分离————分成业务子工程（hilodemo-biz）和APP入口配置子工程(hilodemo)
     * @param separateBizApp true代表分开，false代表揉在一起
     */
    public void setSeparateBizApp(boolean separateBizApp) {
        this.separateBizApp = separateBizApp;
    }

    /**
     * @return the sourceFileUrl
     */
    public String getSourceFileUrl() {
        return sourceFileUrl;
    }

}
